//
// Created by 郝羽 on 2017/11/21.
//

#ifndef RTC_XSWITCH2_ESTIMATOR_HPP
#define RTC_XSWITCH2_ESTIMATOR_HPP

#include <cmath>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>


#include "packet_history.hpp"
#include "rtp/rtcp/transport_feedback.hpp"

class BitrateEstimator {
    static constexpr int64_t BaseTimeRangeMs = (1 << 24) * 64 / 1000;
public:
    BitrateEstimator(const char* id, int start_bitrate, int max_bitrate, bool debug)
            : _id(id),
              _estimation(start_bitrate),
              _start_bitrate(start_bitrate),
              _max_bitrate(max_bitrate) {
        if (debug) {
            char fn[1024] = {0,};
            sprintf(fn, "/tmp/%s.json", id);
            _out = fopen(fn, "w+");
            if (_out) {
                fprintf(_out, "[\n");
            }
        }
    };

    ~BitrateEstimator() {
        if (_out) {
            fprintf(_out, "]\n");
            fclose(_out);
        }
    }

    uint32_t update() {
        int64_t now = get_timestamp_ms();
        int32_t last_transmit_time = 0;
        int32_t n = 0;

        int32_t max_j = 0;
        int32_t min_j = 0;
        int32_t sum_j = 0;

        int32_t r_bytes = 0;
        int64_t r_first_ts = 0;
        int64_t r_last_ts = 0;

        std::vector<int32_t> transmit_times;
        std::ostringstream tstr;
        _recv_history.for_each([&](const PacketInfo& info, bool is_last) {
            if (info.recv_timestamp_ms == 0) {
                return;
            }

            r_bytes += info.bytes;
            if (r_first_ts == 0) {
                r_first_ts = info.recv_timestamp_ms;
            }
            r_last_ts = info.recv_timestamp_ms;

            int32_t t = info.recv_timestamp_ms - info.send_timestamp_ms;
            if (last_transmit_time != 0) {
                int32_t jitter = t - last_transmit_time;
                if (jitter > max_j) {
                    max_j = jitter;
                }
                if (min_j == 0 || std::abs(jitter) < min_j) {
                    min_j = jitter;
                }
                sum_j += jitter;
            }
            last_transmit_time = t;
            n++;
        });

        int32_t delta = 0;
        double avg_j = 0;
        int64_t r_dur = r_last_ts - r_first_ts;
        int32_t r_bps = 0;

        if (n < 8) {
            if (_current_offset_ms && now - _current_offset_ms > 1000) {
                _estimation = _estimation * 0.85;
                std::cout << "|" << _id << "| no feedback in last second, estimation: " << _estimation/1000 << std::endl;
            }
            return _estimation;
        } else {
            avg_j = (double)sum_j / (n - 1);
            if (r_dur != 0 && std::abs(r_dur) > 100) {
                r_bps = r_bytes * 8.0 * 1000 / std::abs(r_dur);
                if (_last_recv_bps) {
                    r_bps = _last_recv_bps * 0.8 + r_bps * 0.2;
                }
                _last_recv_bps = r_bps;
            } else {
                r_bps = _last_recv_bps;
            }
            if (true) {
                if (avg_j >= 5.0) {
                    if (_dec_ts == 0 && _dec2_ts == 0) {
                        _dec_ts = now;
                        _dec2_ts = 0;
                        _inc_ts = 0;
                    } else if (now - _dec_ts > 100 && _dec2_ts == 0) {
                        if (r_bps != 0) {
                            delta = r_bps * -0.15;
                            _estimation = r_bps * 0.85;
                        } else {
                            delta = _estimation * -0.15;
                            _estimation = _estimation * 0.85;
                        }
                        // do not exit decreasing state
                        //_dec_ts = now;
                        _dec_ts = 0;
                        _dec2_ts = 0;
                        _inc_ts = 0;
                        _recv_history.reset();
                    } else if(now - _dec2_ts > 100) {
                        delta = -1 * 0.05 * _estimation;
                        _estimation = _estimation + delta;
                        _dec_ts = 0;
                        _dec2_ts = 0;
                        _inc_ts = 0;
                        _recv_history.reset();
                    }
                } else if (avg_j >= 2.0) {
                    if (_dec2_ts == 0) {
                        _dec_ts = 0;
                        _dec2_ts = now;
                        _inc_ts = 0;
                    } else if (now - _dec2_ts > 100) {
                        delta = -1 * 0.05 * _estimation;
                        _estimation = _estimation + delta;
                        _dec_ts = 0;
                        _dec2_ts = 0;
                        _inc_ts = 0;
                        _recv_history.reset();
                    }
                } else if (std::abs(avg_j) <= 1 && max_j < 100) {
                    if (_inc_ts == 0) {
                        _dec_ts = 0;
                        _dec2_ts = 0;
                        _inc_ts = now;
                    } else if (now - _inc_ts >= _estimation / 1000) {
                        // here we cannot add to r_bps, because that will make estimation hard to climb when bandwitdh is not fully used
                        delta = 10 * 1000;
//                        if (r_bps > 0) {
//                            _estimation = r_bps + delta;
//                        } else {
                            _estimation = _estimation + delta;
//                        }
                        // avoid very high estimation when publisher bw is low
//                        if (r_bps != 0 && _estimation > r_bps * 1.5) {
//                            _estimation = r_bps * 1.5;
//                        }
                        if (_estimation > _max_bitrate) {
                            _estimation = _max_bitrate;
                        }
                        _dec_ts = 0;
                        _dec2_ts = 0;
                        _inc_ts = 0;
                        _recv_history.reset();
                    }
                } else {
                    delta = 0;
                    _inc_ts = 0;
                    _dec_ts = 0;
                    _dec2_ts = 0;
                    _recv_history.reset();
                }
            }
        }
// debug code
        std::cout << "|" << _id << "| now: " << now;
        std::cout << " recv: " << std::setw(4) << r_bps / 1000;
        std::cout << " rdur: " << std::setw(4) << r_dur;
        if (delta > 0) {
            std::cout << " ⬆" << std::setw(4) << delta/1000;
        } else if (delta < 0) {
            std::cout << " ⬇" << std::setw(4) << delta/1000;
        } else if (_inc_ts){
            std::cout << " ⬆?   ";
        } else if (_dec_ts) {
            std::cout << " ⬇⬇? ";
        } else if (_dec2_ts) {
            std::cout << " ⬇?   ";
        } else {
            std::cout << "       ";
        }
        std::cout << " loss: " << std::setw(4) << int(_loss_ratio * 100) << "%";
        std::cout << " estimation: " << std::setw(5) << _estimation/1000;
        std::cout << " jitter-avg: " << std::setw(6) << std::setprecision(2) << avg_j;
        std::cout << " jitter-max: " << std::setw(6) << std::setprecision(2) << max_j;
        std::cout << std::endl;

        if (_out) {
            rapidjson::StringBuffer buf;
            rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
            writer.StartObject();
            writer.Key("recv-bps");
            writer.Int(r_bps);
            writer.Key("loss-ratio");
            writer.Double(_loss_ratio);
            writer.Key("estimation");
            writer.Int(_estimation);
            writer.Key("inc-ts");
            writer.Int64(_inc_ts);
            writer.Key("dec-ts");
            writer.Int64(_dec_ts);
            writer.Key("n");
            writer.Int(n);
            writer.Key("jitter-avg");
            writer.Double(avg_j);
            writer.Key("jitter-max");
            writer.Int(max_j);
            writer.EndObject();
            std::string str = buf.GetString();
            if (_out_first) {
                fprintf(_out, "\t%s\n", str.c_str());
                _out_first = false;
            } else {
                fprintf(_out, "\t,%s\n", str.c_str());
            }
        }

        return _estimation;
    };

    void on_transport_feedback(const TransportFeedback& fb) {
        uint64_t timestamp_ms = fb.base_time;
        if (_last_timestamp_ms == 0) {
            _current_offset_ms = get_timestamp_ms();
        } else {
            int64_t delta = timestamp_ms - _last_timestamp_ms;
            if (std::abs(delta - BaseTimeRangeMs) < std::abs(delta)) {
                //std::cout << "transport feedback base timestamp wrap backward" << std::endl;
                delta -= BaseTimeRangeMs;
            } else if (std::abs(delta + BaseTimeRangeMs) < std::abs(delta)) {
                //std::cout << "transport feedback base timestamp wrap forward" << std::endl;
                delta += BaseTimeRangeMs;
            }
            _current_offset_ms += delta;
        }
        _last_timestamp_ms = timestamp_ms;
        int64_t d = 0;
        fb.for_each([&](uint16_t seq, int32_t delta) {
            d += delta;
            int64_t recv_ts = _current_offset_ms + d;
            PacketInfo info;
            if (_send_history.get(info, seq)) {
                _recv_history.insert_packet(info.seq, info.bytes, info.send_timestamp_ms, recv_ts);
            }
            //std::cout << "transport feedback seq: " << seq << " delta: " << d << " recv: " << recv_ts <<std::endl;
        });
    };

    void on_packet_sent(uint16_t seq, int16_t bytes, int64_t ts) {
        _send_history.insert_packet(seq, bytes, ts, 0);
    };

    void on_packet_recv(uint16_t seq, int16_t bytes, int64_t send_ts, int64_t recv_ts) {
        _recv_history.insert_packet(seq, bytes, send_ts, recv_ts);
    };

    void on_loss_ratio(double loss_ratio) {
        _loss_ratio = loss_ratio;
    };

    bool have_transport_feedback() {
        return _current_offset_ms != 0;
    };

private:
    std::string _id;
    int _start_bitrate;
    int _max_bitrate;

    PacketHistory<128> _send_history;
    PacketHistory<32> _recv_history;

    int64_t _last_timestamp_ms = 0;
    int64_t _current_offset_ms = 0;

    int32_t _estimation;

    double _loss_ratio = 0;

    int64_t _inc_ts = 0;
    int64_t _dec_ts = 0;
    int64_t _dec2_ts = 0;

    FILE* _out = nullptr;
    bool _out_first = true;

    int _last_recv_bps = 0;
};
#endif //RTC_XSWITCH2_ESTIMATOR_HPP
